﻿using System;
using System.Linq;
using FakeItEasy;
using NJabber.Client.StanzaFactories;
using NJabber.Stream;
using NJabber.Stream.Elements;
using NUnit.Framework;

namespace NJabber.Client
{
    [TestFixture]
    public class XmppClientTests
    {
        private IClientRuntimeFactory factory;
        private XmppClientRuntime runtime;
        private XmppClient client;
        JID entityJid = "amy@lee.com";

        [SetUp]
        public void TestSetup()
        {
            factory = A.Fake<IClientRuntimeFactory>();
            runtime = A.Fake<XmppClientRuntime>();
            A.CallTo(() => runtime.Connection).Returns(A.Fake<IXmppConnection>());
            A.CallTo(() => factory.CreateRuntime()).Returns(runtime);
            client = new XmppClient(factory);
            client.Connect();
        }

        protected IStanzaStream Stream
        {
            get { return runtime.Connection.Stream; }
        }

        #region Roster tests

        [Test]
        public void RosterItems_ReceivedRosterResult_CorrectlyUpdatesRosterCache()
        {
            RosterItem[] items = Enumerable.Range(0, 5).Select(i => new RosterItem("item" + i, "jid" + i + "domain.org")).ToArray();
            Stanza rosterResult = CreateRosterResult(items);

            Stream.StanzaReceived += Raise.With(Stream, new StanzaReceivedEventArgs(rosterResult)).Now;

            CollectionAssert.AreEquivalent(client.RosterItems, items);
        }

        private Stanza CreateRosterResult(RosterItem[] items)
        {
            IqStanza rosterResult = new IqStanza(IqStanzaTypes.Result);
            IqQueryElement query = rosterResult.AddQueryElement(XmppNamespaces.Roster);

            foreach (RosterItem item in items)
            {
                query.AddContentItem(item);
            }
            return rosterResult;
        }

        [Test]
        public void RosterItems_ReceivedRosterPush_CorrectlyUpdatesRosterCache()
        {
            RosterItem item = new RosterItem("item", "item@jid.com");
            Stanza rosterPush = CreateRosterPush(item);
            RosterCache rosterCache = new RosterCache(A.Fake<IRosterSynchronizer>());
            A.CallTo(() => runtime.RosterCache).Returns(rosterCache);
            Stream.StanzaReceived += Raise.With(client, new StanzaReceivedEventArgs(rosterPush)).Now;

            Assert.That(client.RosterItems.Count(), Is.EqualTo(1));
            CollectionAssert.Contains(client.RosterItems, item);
        }

        private Stanza CreateRosterPush(RosterItem item)
        {
            IqStanza rosterPush = new IqStanza(IqStanzaTypes.Set);
            rosterPush.AddQueryElement(XmppNamespaces.Roster).AddContentItem(item);
            return rosterPush;
        }

        #endregion

        #region Presence tests

        [Test]
        public void UpdatePresence_AnyPresenceData_SendsStanza()
        {
            client.PresenceStatus = new PresenceStatus("some@jid.com", true, AvailabilityStatus.DoNotDisturb);

            client.UpdatePresence();

            A.CallTo(() => Stream.Write(A<Stanza>._)).MustHaveHappened();
        }

        #endregion

        #region Subscription managment

        [Test]
        public void SendSubscriptionRequest_AnyJid_SendsCorrectPresenceStanza()
        {
            JID subscriptionTarget = "arnold@s.com";
            PresenceStanza expectedStanza = new PresenceStanza {To = subscriptionTarget, Type = PresenceStanzaType.Subscribe};

            client.SendSubscriptionRequest(subscriptionTarget);

            A.CallTo(() => Stream.Write(expectedStanza)).MustHaveHappened();
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void SendSubscriptionRequest_NullParameter_ThrowsException()
        {
            client.SendSubscriptionRequest(null);
        }

        #endregion

        #region SendStanza method tests

        [Test]
        public void SendStanza_AnyStanza_SendsSameStanza()
        {
            Stanza stanza = new IqStanza(IqStanzaTypes.Get);

            client.SendStanza(stanza);

            A.CallTo(() => Stream.Write(stanza)).MustHaveHappened();
        }

        [Test]
        [ExpectedException(typeof(InvalidOperationException))]
        public void SendStanza_NotConnected_ThrowsException()
        {
            client.SendStanza(new IqStanza());
        }

        [Test]
        [ExpectedException(typeof(NullReferenceException))]
        public void SendStanza_NullParam_ThrowsException()
        {
            client.SendStanza(null);
        }

        #endregion

        [Test]
        public void ProcessReceivedStanza_Stanza_InvokesEachHandler()
        {
            Stanza stanza = new PresenceStanza("some-presence");
            IStanzaHandler[] handlers = CreateFakeHandlers();
            foreach (IStanzaHandler handler in handlers)
            {
                A.CallTo(() => handler.ProcessStanza(A<XmppClientRuntime>._, stanza)).Returns(true);
            }
            SetHandlers(handlers);
            client.Connect();

            Stream.StanzaReceived += Raise.With(Stream, new StanzaReceivedEventArgs(stanza)).Now;

            foreach (IStanzaHandler handler in handlers)
                A.CallTo(() => handler.ProcessStanza(A<XmppClientRuntime>._, A<Stanza>._))
                 .MustHaveHappened();
        }

        [Test]
        public void ProcessReceivedStanza_OneHandlerReturnsFalse()
        {
            Stanza stanza = new IqStanza("some-iq");
            IStanzaHandler[] handlers = CreateFakeHandlers(3);
            A.CallTo(() => handlers[0].ProcessStanza(A<XmppClientRuntime>._, stanza)).Returns(true);
            A.CallTo(() => handlers[1].ProcessStanza(A<XmppClientRuntime>._, stanza)).Returns(false);
            A.CallTo(() => handlers[2].ProcessStanza(A<XmppClientRuntime>._, stanza)).Returns(true);
            SetHandlers(handlers);
            client.Connect();

            Stream.StanzaReceived += Raise.With(Stream, new StanzaReceivedEventArgs(stanza)).Now;

            A.CallTo(() => handlers[0].ProcessStanza(A<XmppClientRuntime>._, stanza)).MustHaveHappened();
            A.CallTo(() => handlers[1].ProcessStanza(A<XmppClientRuntime>._, stanza)).MustHaveHappened();
            A.CallTo(() => handlers[2].ProcessStanza(A<XmppClientRuntime>._, stanza)).MustNotHaveHappened();
        }

        private IStanzaHandler[] CreateFakeHandlers(int count = 5)
        {
            return Enumerable.Range(1, count).Select(i => A.Fake<IStanzaHandler>()).ToArray();
        }

        private void SetHandlers(IStanzaHandler[] handlers)
        {
            A.CallTo(() => runtime.StanzaHandlers).Returns(handlers);
        }
    }
}
